package service

import (
	"batbat-backend/app/dao"
	"batbat-backend/app/model"
	"batbat-backend/library/taptap"
	"context"
	"fmt"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gconv"
	"runtime"
	"sync"
)

// Taptap taptap游戏服务, 主要任务为爬取taptap网站的数据
var Taptap = taptapService{}

type taptapService struct{}

// GetTaptapTopGame 获取Taptap的TOP150数据并保存进入数据库, 返回taptapId列表
func (t *taptapService) GetTaptapTopGame() {
	startTime := gtime.Now()
	// 通过爬虫爬取游戏id列表
	ids, err := taptap.GetGameIds()
	if err != nil {
		g.Log().Errorf("获取游戏id列表失败: %v", err)
		return
	}
	if ids == nil || len(ids) == 0 {
		g.Log().Error("未获取到任何游戏")
		return
	}
	var (
		ctx      = context.TODO()                               // 执行上下文
		jobs     = make(chan *model.TaptapIdRankPair, len(ids)) // 任务管道
		subjobs  = make(chan *model.Game, len(ids))             // 子任务管道
		poolSize = runtime.GOMAXPROCS(runtime.NumCPU())         // 并发数
		wg       = new(sync.WaitGroup)                          // 并发等待
		subwg    = new(sync.WaitGroup)                          // 子任务并发等待
	)
	wg.Add(poolSize)
	for i := 0; i < poolSize; i++ {
		go func() {
			defer wg.Done() // 退出前声明任务执行完成
			for job := range jobs {
				// 获取游戏资料
				detail, err := taptap.GetDetail(job)
				if err != nil {
					g.Log().Errorf("获取游戏详情失败, err=%v", err)
					continue
				}
				game, err := dao.Game.SaveGame(ctx, detail)
				if err != nil {
					g.Log().Errorf("保存数据失败, err=%v", err)
					continue
				}
				// 获取游戏评论
				subjobs <- game
			}
		}()
	}
	subwg.Add(poolSize)
	for i := 0; i < poolSize; i++ {
		go func() { // 获取游戏评论
			defer subwg.Done() // 退出前声明任务执行完成
			for subjob := range subjobs {
				t.getTaptapGameReview(ctx, subjob)
			}
		}()
	}
	// 发出任务
	for i, id := range ids {
		jobs <- &model.TaptapIdRankPair{
			TaptapId: id,
			Rank:     i + 1,
		}
	}
	// 任务发出完成
	close(jobs)
	// 等待执行完成
	wg.Wait()
	// 关闭子任务管道
	close(subjobs)
	// 等待子任务执行完成
	subwg.Wait()
	g.Log().Infof("[获取TOP150]执行完成, 耗时: %v", gtime.Now().Sub(startTime))
}

// getTaptapGameReviewWorker 获取Taptap的游戏评论数据并保存进入数据库的工作者
func (t *taptapService) getTaptapGameReview(ctx context.Context, game *model.Game) {
	// 获取游戏的评论数据
	reviews, users, err := taptap.GetReview(game)
	if err != nil {
		g.Log().Errorf("获取游戏评论失败, err=%v", err)
		return
	}
	// 开启事务, 保存数据
	err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
		// 保存评论的用户
		_, err = dao.User.Ctx(ctx).TX(tx).
			FieldsEx(dao.User.Columns.Id,
				dao.User.Columns.CreateTime,
				dao.User.Columns.Birthday).
			Data(users).
			Batch(1000).
			Save()
		if err != nil {
			return gerror.Newf("保存发起评论的用户时发生错误, err=%v", err)
		}

		// 将review切片转换为map切片, 并将uid项改为sql语句, 方便执行插入
		reviewMaps := gconv.SliceMapDeep(reviews)
		for i := range reviewMaps {
			reviewMaps[i]["uid"] = gdb.Raw(
				fmt.Sprintf("(select id from user where phone_number = '%d')", reviewMaps[i]["uid"]))
		}
		_, err = dao.GameReview.Ctx(ctx).TX(tx).
			FieldsEx(dao.GameReview.Columns.Id,
				dao.GameReview.Columns.DeleteTime).
			Data(reviewMaps).
			Batch(1000).
			Save()
		if err != nil {
			return gerror.Newf("保存评论数据时发生错误, err=%v", err)
		}
		return nil
	})
	if err != nil {
		g.Log().Errorf("保存游戏评论时发生错误, err=%v", err)
	}
}
